package com.ruyiadmin.springboot.common.interceptors.core;

import cn.hutool.core.date.DateUtil;
import com.alibaba.fastjson.JSON;
import com.auth0.jwt.interfaces.DecodedJWT;
import com.ruyiadmin.springboot.common.annotations.core.AllowAnonymous;
import com.ruyiadmin.springboot.common.awares.core.RuYiAdminContextAware;
import com.ruyiadmin.springboot.common.beans.system.JwtSettings;
import com.ruyiadmin.springboot.common.beans.system.SystemConfig;
import com.ruyiadmin.springboot.common.components.core.RuYiRedisComponent;
import com.ruyiadmin.springboot.common.components.core.RuYiTokenComponent;
import com.ruyiadmin.springboot.common.components.system.RuYiLogComponent;
import com.ruyiadmin.springboot.common.core.business.enums.OperationType;
import com.ruyiadmin.springboot.common.core.system.entities.BroadcastMessage;
import com.ruyiadmin.springboot.common.core.system.entities.SystemMessage;
import com.ruyiadmin.springboot.common.core.system.enums.MessageLevel;
import com.ruyiadmin.springboot.common.core.system.enums.MessageType;
import com.ruyiadmin.springboot.common.events.system.SysLogEvent;
import com.ruyiadmin.springboot.common.exceptions.RuYiAdminCustomException;
import com.ruyiadmin.springboot.common.utils.system.RuYiJwtTokenUtil;
import com.ruyiadmin.springboot.domain.dto.system.SysUserDTO;
import com.ruyiadmin.springboot.domain.entity.system.SysLog;
import com.ruyiadmin.springboot.domain.entity.system.SysRecord;
import com.ruyiadmin.springboot.service.iservices.system.ISysRecordService;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.apache.http.HttpStatus;
import org.jetbrains.annotations.NotNull;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.context.annotation.Configuration;
import org.springframework.jms.core.JmsMessagingTemplate;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.web.method.HandlerMethod;
import org.springframework.web.servlet.HandlerInterceptor;
import org.springframework.web.servlet.ModelAndView;

import javax.annotation.Resource;
import javax.jms.Topic;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.lang.reflect.Method;
import java.util.Date;

/**
 * <p>
 * RuYiAdmin拦截器
 * </p>
 *
 * @author RuYiAdmin
 * @since 2022-07-16
 */
@Slf4j
@EnableConfigurationProperties({JwtSettings.class, SystemConfig.class})
@Configuration
public class RuYiAdminAuthenticationInterceptor implements HandlerInterceptor {

    //region 拦截器私有属性

    @Resource
    private JwtSettings jwtSettings;
    @Resource
    private RuYiRedisComponent redisUtils;
    @Resource
    private SystemConfig systemConfig;
    @Resource
    private RuYiTokenComponent tokenUtil;
    @Resource
    private ISysRecordService recordService;
    @Resource
    private RuYiLogComponent logUtil;
    @Resource
    private Topic topic;
    @Resource
    private JmsMessagingTemplate jmsMessagingTemplate;

    //endregion

    //region 系统前置拦截

    @Override
    @Transactional(rollbackFor = Exception.class)
    public boolean preHandle(@NotNull HttpServletRequest httpServletRequest,
                             @NotNull HttpServletResponse httpServletResponse,
                             @NotNull Object object) throws Exception {

        // 如果不是映射到方法直接通过
        if (!(object instanceof HandlerMethod)) {
            return true;
        }

        HandlerMethod handlerMethod = (HandlerMethod) object;
        Method method = handlerMethod.getMethod();

        //region 处理匿名方法

        if (method.isAnnotationPresent(AllowAnonymous.class)) {
            //检查是否有AllowAnonymous注释，有则跳过认证
            AllowAnonymous allowAnonymous = method.getAnnotation(AllowAnonymous.class);
            if (allowAnonymous.required()) {
                return true;
            }
        }

        //endregion

        //region 处理白名单

        String path = httpServletRequest.getRequestURI();
        if (!StringUtils.isEmpty(this.systemConfig.getWhiteList())) {
            String[] array = this.systemConfig.getWhiteList().split(",");
            for (String item : array) {
                if (path.endsWith(item)) {
                    return true;
                }
            }
        }

        //endregion

        //region 验证JwtToken

        if (this.systemConfig.isCheckJwtToken()) {
            try {
                // 从请求头中取出 token
                String jwtToken = httpServletRequest.getHeader("Authorization");
                // 执行认证
                if (StringUtils.isEmpty(jwtToken)) {
                    httpServletResponse.sendError(HttpStatus.SC_UNAUTHORIZED, "unauthorized jwt token");
                    return false;
                }

                DecodedJWT decodedjwt = RuYiJwtTokenUtil.decodeJwt(this.jwtSettings,
                        jwtToken.replace("Bearer ", ""));

                String issuer = decodedjwt.getIssuer();
                String audience = decodedjwt.getAudience().get(0);
                Date expiresDate = decodedjwt.getExpiresAt();
                String id = decodedjwt.getKeyId();
                String sub = decodedjwt.getClaim("sub").toString().replace("\"", "");
                String jti = decodedjwt.getClaim("jti").toString().replace("\"", "");

                if (!issuer.equals(this.jwtSettings.getIssuer())) {
                    httpServletResponse.sendError(HttpStatus.SC_UNAUTHORIZED, "unauthorized jwt token");
                    return false;
                }
                if (!audience.equals(this.jwtSettings.getAudience())) {
                    httpServletResponse.sendError(HttpStatus.SC_UNAUTHORIZED, "unauthorized jwt token");
                    return false;
                }
                Object value = this.redisUtils.get(id);
                String tokenId = value == null ? "" : value.toString();
                if (!StringUtils.isEmpty(tokenId) && !id.equals(tokenId)) {
                    httpServletResponse.sendError(HttpStatus.SC_UNAUTHORIZED, "unauthorized jwt token");
                    return false;
                }
                if (!sub.equals(this.jwtSettings.getDefaultUser())) {
                    httpServletResponse.sendError(HttpStatus.SC_UNAUTHORIZED, "unauthorized jwt token");
                    return false;
                }
                if (!jti.equals(id)) {
                    httpServletResponse.sendError(HttpStatus.SC_UNAUTHORIZED, "unauthorized jwt token");
                    return false;
                }

                //JwtToken超时验证
                if (DateUtil.parse(expiresDate.toString()).getTime() < DateUtil.parse(DateUtil.now()).getTime()) {
                    httpServletResponse.setHeader("act", "expired");
                    httpServletResponse.sendError(HttpStatus.SC_UNAUTHORIZED);
                    return false;
                }
            } catch (Exception exception) {
                throw new RuYiAdminCustomException("unauthorized jwt token");
            }
        }

        //endregion

        //region 验证系统Token

        if (this.systemConfig.isCheckToken()) {
            if (StringUtils.isEmpty(httpServletRequest.getHeader("token"))) {
                httpServletResponse.sendError(HttpStatus.SC_FORBIDDEN, "token is necessary");
                return false;
            } else {
                //region Token续时操作

                String token = this.tokenUtil.getToken();
                if (token == null) {
                    httpServletResponse.sendError(HttpStatus.SC_UNAUTHORIZED, "invalid user token");
                    return false;
                }

                Object value = this.redisUtils.get(token);
                if (value == null) {
                    httpServletResponse.sendError(HttpStatus.SC_UNAUTHORIZED, "invalid user token");
                    return false;
                }

                //获取用户
                SysUserDTO user = JSON.parseObject(value.toString(), SysUserDTO.class);
                if (user != null) {
                    String salt = this.tokenUtil.getSalt();
                    SysRecord record = this.recordService.getById(salt);
                    if (record == null) {

                        //region 记录合法salt

                        record = new SysRecord();
                        record.setId(salt);
                        this.recordService.save(record);

                        //endregion

                        //region 用户token续时

                        int tokenExpiration = this.systemConfig.getUserTokenExpiration() * 60;
                        this.redisUtils.expire(token, tokenExpiration);

                        //endregion

                    } else {

                        //region 记录Token劫持行为

                        //region Token劫持记录审计日志

                        SysLog log = logUtil.getSysLog();
                        log.setOperationType(OperationType.TokenHijacked.ordinal());
                        log.setRemark(user.getLogonName() + "/" + user.getDisplayName()
                                + "的Token被劫持，使用口令为" + token + "，请管理员警惕、关注！");

                        // 发送异步日志事件
                        RuYiAdminContextAware.publishEvent(new SysLogEvent(log));

                        //endregion

                        //region 发送Token劫持告警消息

                        SystemMessage msg = new SystemMessage();
                        msg.setMessage("Broadcast");
                        msg.setMessageType(MessageType.Broadcast);

                        BroadcastMessage broadcastMessage = new BroadcastMessage();
                        broadcastMessage.setTitle("用户Token劫持告警");
                        broadcastMessage.setMessage(log.getRemark());
                        broadcastMessage.setMessageLevel(MessageLevel.Severity);

                        msg.setObject(broadcastMessage);

                        this.jmsMessagingTemplate.convertAndSend(topic, JSON.toJSONString(msg));

                        //endregion

                        //endregion

                        httpServletResponse.sendError(HttpStatus.SC_FORBIDDEN, "illegal access");
                        return false;
                    }
                } else {
                    httpServletResponse.sendError(HttpStatus.SC_UNAUTHORIZED, "invalid user token");
                    return false;
                }

                //endregion
            }
        }

        //endregion

        //region 其他头部验证

        if (!StringUtils.isEmpty(systemConfig.getHeaderConfig())) {
            String[] array = systemConfig.getHeaderConfig().split(",");
            for (String item : array) {
                if (StringUtils.isEmpty(httpServletRequest.getHeader(item))) {
                    httpServletResponse.sendError(HttpStatus.SC_FORBIDDEN, item.toLowerCase() + " is necessary");
                    return false;
                }
            }
        }

        //endregion

        return true;
    }

    //endregion

    //region 系统其他拦截

    @Override
    public void postHandle(@NotNull HttpServletRequest httpServletRequest,
                           @NotNull HttpServletResponse httpServletResponse,
                           @NotNull Object o, ModelAndView modelAndView) throws Exception {

    }

    @Override
    public void afterCompletion(@NotNull HttpServletRequest httpServletRequest,
                                @NotNull HttpServletResponse httpServletResponse,
                                @NotNull Object o, Exception e) throws Exception {
    }

    //endregion

}
